#include "OSMTileSource.h"

#include "guts/MapGraphicsNetwork.h"

#include <QCoreApplication>
#include <QMessageBox>
#include <cmath>
#include <QPainter>
#include <QStringBuilder>
#include <QtDebug>
#include <QNetworkReply>
#include <QTimer>
#include <QTime>

const qreal PI = 3.14159265358979323846;
const qreal deg2rad = PI / 180.0;
const qreal rad2deg = 180.0 / PI;

OSMTileSource::OSMTileType OSMTileSource::tileTypes[TOTAL_TYPES] = { OSMTiles,TDTTiles };

QString OSMTileSource::getTileTypeName(OSMTileType tileType)
{
    switch(tileType)
    {
    case OSMTiles:
        return QObject::tr("OpenStreetMap");
        break;
    case TDTTiles:
        return QObject::tr("TianDiTu");
        break;
    default:
        return QObject::tr("UnknownTiles");
        break;
    }
    return "";
}

OSMTileSource::OSMTileSource(OSMTileType tileType) :
    MapTileSource(), _tileType(tileType)
{
    this->setCacheMode(MapTileSource::DiskAndMemCaching);
    // 初始化随机数生成器
    qsrand(QTime::currentTime().msec());
    
    // 设置默认配置文件路径（应用程序所在目录的config.ini）
    _configFilePath = QCoreApplication::applicationDirPath() + "/config.ini";
    
    // 尝试从配置文件加载设置
    reloadSettings();
}

OSMTileSource::~OSMTileSource()
{
    qDebug() << this << this->name() << "Destructing";
}

QPointF OSMTileSource::ll2qgs(const QPointF &ll, quint8 zoomLevel) const
{
    const qreal tilesOnOneEdge = pow(2.0,zoomLevel);
    const quint16 tileSize = this->tileSize();
    qreal x = (ll.x()+180) * (tilesOnOneEdge*tileSize)/360; // coord to pixel!
    qreal y = (1-(log(tan(PI/4+(ll.y()*deg2rad)/2)) /PI)) /2  * (tilesOnOneEdge*tileSize);

    return QPoint(int(x), int(y));
}

QPointF OSMTileSource::qgs2ll(const QPointF &qgs, quint8 zoomLevel) const
{
    const qreal tilesOnOneEdge = pow(2.0,zoomLevel);
    const quint16 tileSize = this->tileSize();
    qreal longitude = (qgs.x()*(360/(tilesOnOneEdge*tileSize)))-180;
    qreal latitude = rad2deg*(atan(sinh((1-qgs.y()*(2/(tilesOnOneEdge*tileSize)))*PI)));

    return QPointF(longitude, latitude);
}

quint64 OSMTileSource::tilesOnZoomLevel(quint8 zoomLevel) const
{
    return pow(4.0,zoomLevel);
}

quint16 OSMTileSource::tileSize() const
{
    return 256;
}

quint8 OSMTileSource::minZoomLevel(QPointF ll)
{
    Q_UNUSED(ll)
    return 0;
}

quint8 OSMTileSource::maxZoomLevel(QPointF ll)
{
    Q_UNUSED(ll)
    return 18;
}

QString OSMTileSource::name() const
{
    switch(_tileType)
    {
    case OSMTiles:
        return "OpenStreetMap";
        break;
    case TDTTiles:
        return "TianDiTu";
        break;
    default:
        return "UnknownTiles";
        break;
    }
}

QString OSMTileSource::tileFileExtension() const
{
    if (_tileType == OSMTiles)
        return "png";
    else if(TDTTiles == _tileType)
        return "jpg";
    else
        return "jpg";
}

//protected
void OSMTileSource::fetchTile(quint32 x, quint32 y, quint8 z)
{
    // 如果启用了MBTiles，首先尝试从MBTiles获取瓦片
    if (_useMBTiles)
    {
        QImage* tileImage = getTileFromMBTiles(x, y, z);
        if (tileImage != nullptr)
        {
            qDebug() << "从MBTiles获取瓦片成功:" << x << y << z;
            // 通知客户端瓦片已获取
            this->prepareNewlyReceivedTile(x, y, z, tileImage, QDateTime::currentDateTimeUtc().addDays(7));
            return;
        }
        else
        {
            qDebug() << "MBTiles中未找到瓦片，尝试从网络获取:" << x << y << z;
        }
    }
    
    // 如果MBTiles未启用或未找到瓦片，则从网络获取
    MapGraphicsNetwork * network = MapGraphicsNetwork::getInstance();

    // 先创建cacheID
    const QString cacheID = this->createCacheID(x,y,z);
    if (_pendingRequests.contains(cacheID))
        return;
    _pendingRequests.insert(cacheID);

    QString host;
    QString url;

    //Figure out which server to request from based on our desired tile type
    if (_tileType == OSMTiles)
    {
        host = "https://b.tile.openstreetmap.org";
        url = "/%1/%2/%3.png";
    }
    else if (TDTTiles == _tileType)
    {
        // 检查api_key是否为空，如果为空则不进行网络请求
        if (_tdtKey.isEmpty())
        {
            qWarning() << "天地图API密钥为空，禁止从网络获取瓦片数据:" << z << x << y;
            _pendingRequests.remove(cacheID);
            return;
        }
        
        // 使用随机选择的服务器以避免单一服务器限流
        const QStringList servers = {"t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7"};
        const QString server = servers.at(qrand() % servers.size());
        host = "http://" + server + ".tianditu.gov.cn";
        url = "/DataServer?T=img_w&x=%2&y=%3&l=%1&tk=" + _tdtKey;
    }

    //Build the request
    const QString fetchURL = url.arg(QString::number(z),
                                     QString::number(x),
                                     QString::number(y));
    QNetworkRequest request(QUrl(host + fetchURL));

    request.setHeader(QNetworkRequest::ContentTypeHeader, "image/jpeg");
    
    // 启用重定向
    request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
    
    // 设置请求超时
    request.setTransferTimeout(10000); // 10秒超时
    
    // 设置缓存策略为正常浏览器行为
    request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
    
    //Send the request and setupd a signal to ensure we're notified when it finishes
    QNetworkReply * reply = network->get(request);
    _pendingReplies.insert(reply,cacheID);

    connect(reply,
            SIGNAL(finished()),
            this,
            SLOT(handleNetworkRequestFinished()));
}

//private slot
void OSMTileSource::setConfigFilePath(const QString &configPath)
{
    _configFilePath = configPath;
    reloadSettings();
}

void OSMTileSource::reloadSettings()
{
    QSettings settings(_configFilePath, QSettings::IniFormat);
    
    // 读取天地图API密钥
    if (settings.contains("TDT/api_key"))
    {
        _tdtKey = settings.value("TDT/api_key").toString();
        qDebug() << "天地图API密钥已从配置文件加载:" << _configFilePath;
    }
    else if (!_configFilePath.isEmpty())
    {
        QMessageBox::warning(nullptr, "配置警告", "配置文件中未找到天地图API密钥，使用默认值:" + _configFilePath);
    }
    
    // 读取MBTiles配置
    _useMBTiles = settings.value("MBTiles/use", false).toBool();
    _mbtilesFilePath = settings.value("MBTiles/path", "").toString();
    
    qDebug() << "MBTiles配置已加载:";
    qDebug() << "  使用MBTiles:" << (_useMBTiles ? "是" : "否");
    qDebug() << "  MBTiles路径:" << _mbtilesFilePath;
    
    // 如果配置了使用MBTiles，尝试打开数据库
    if (_useMBTiles && !_mbtilesFilePath.isEmpty())
    {
        // 关闭之前可能存在的连接
        if (_mbtilesDB.isOpen())
        {
            _mbtilesDB.close();
        }
        
        // 移除之前的连接（如果存在）
        if (QSqlDatabase::contains("mbtilesConnection"))
        {
            _mbtilesDB = QSqlDatabase::database("mbtilesConnection");
            _mbtilesDB.close();
            QSqlDatabase::removeDatabase("mbtilesConnection");
        }
        
        // 创建新的数据库连接
        _mbtilesDB = QSqlDatabase::addDatabase("QSQLITE", "mbtilesConnection");
        _mbtilesDB.setDatabaseName(_mbtilesFilePath);
        
        // 尝试打开数据库
        if (!_mbtilesDB.open())
        {
            qWarning() << "无法打开MBTiles文件:" << _mbtilesFilePath << "错误:" << _mbtilesDB.lastError().text();
            _useMBTiles = false; // 如果无法打开，禁用MBTiles
        }
        else
        {
            qDebug() << "成功打开MBTiles文件:" << _mbtilesFilePath;
        }
    }
}

QImage* OSMTileSource::getTileFromMBTiles(quint32 x, quint32 y, quint8 z)
{
    // 检查是否启用了MBTiles且数据库已打开
    if (!_useMBTiles || !_mbtilesDB.isOpen())
    {
        return nullptr;
    }
    
#if 0
    // MBTiles使用的是XYZ瓦片格式，但Y轴可能需要翻转
    // 计算翻转后的Y值（对于TMS格式的瓦片）
    quint32 tmsY = (1 << z) - 1 - y;
#else
    quint32 tmsY = y;
#endif
    // 查询瓦片数据
    QSqlQuery query(_mbtilesDB);
    query.prepare("SELECT tile_data FROM tiles WHERE zoom_level = ? AND tile_column = ? AND tile_row = ?");
    query.addBindValue(z);
    query.addBindValue(x);
    query.addBindValue(tmsY); // 使用翻转后的Y值
    
    if (!query.exec())
    {
        qWarning() << "MBTiles查询错误:" << query.lastError().text();
        return nullptr;
    }
    
    // 检查是否找到瓦片
    if (query.next())
    {
        QByteArray tileData = query.value(0).toByteArray();
        QImage* image = new QImage();
        
        if (image->loadFromData(tileData))
        {
            return image;
        }
        else
        {
            qWarning() << "无法从MBTiles数据创建图像";
            delete image;
            return nullptr;
        }
    }
    
    // 未找到瓦片
    return nullptr;
}

void OSMTileSource::handleNetworkRequestFinished()
{
    QObject * sender = QObject::sender();
    QNetworkReply * reply = qobject_cast<QNetworkReply *>(sender);

    if (reply == 0)
    {
        qWarning() << "QNetworkReply cast failure";
        return;
    }

    /*
      We can do this here and use reply later in the function because the reply
      won't be deleted until execution returns to the event loop.
    */
    reply->deleteLater();

    if (!_pendingReplies.contains(reply))
    {
        qWarning() << "Unknown QNetworkReply";
        return;
    }

    //get the cacheID
    const QString cacheID = _pendingReplies.take(reply);
    _pendingRequests.remove(cacheID);

    //If there was a network error, handle it with retry mechanism
    if (reply->error() != QNetworkReply::NoError)
    {
        qDebug() << "Network Error:" << reply->errorString();
        
        // 重试机制：如果是Forbidden错误，尝试重新请求
        if (reply->errorString().contains("Forbidden"))
        {
            // 解析cacheID获取x,y,z坐标
            quint32 x,y,z;
            if (MapTileSource::cacheID2xyz(cacheID,&x,&y,&z))
            {
                qDebug() << "Retrying tile request for" << x << y << z;
                // 延迟一小段时间后重试
                QTimer::singleShot(100, this, [=]() {
                    this->fetchTile(x, y, z);
                });
            }
        }
        return;
    }

    //Convert the cacheID back into x,y,z tile coordinates
    quint32 x,y,z;
    if (!MapTileSource::cacheID2xyz(cacheID,&x,&y,&z))
    {
        qWarning() << "Failed to convert cacheID" << cacheID << "back to xyz";
        return;
    }

    QByteArray bytes = reply->readAll();
    QImage * image = new QImage();

    if (!image->loadFromData(bytes))
    {
        delete image;
        qWarning() << "Failed to make QImage from network bytes";
        return;
    }

    //Figure out how long the tile should be cached
    QDateTime expireTime;
    if (reply->hasRawHeader("Cache-Control"))
    {
        //We support the max-age directive only for now
        const QByteArray cacheControl = reply->rawHeader("Cache-Control");
        QRegExp maxAgeFinder("max-age=(\\d+)");
        if (maxAgeFinder.indexIn(cacheControl) != -1)
        {
            bool ok = false;
            const qint64 delta = maxAgeFinder.cap(1).toULongLong(&ok);

            if (ok)
                expireTime = QDateTime::currentDateTimeUtc().addSecs(delta);
        }
    }

    //Notify client of tile retrieval
    this->prepareNewlyReceivedTile(x,y,z, image, expireTime);
}
